﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace FileTransferProtocol
{
    public static class Assertion
    {
        public static void Assert(bool assert, string message)
        {
            if (!assert)
                throw new Exception(message);
        }
    }

    public class ReceiverHandle
    {
        /// <summary>
        /// 运行前的检查
        /// </summary>
        /// <param name="receiver"></param>
        /// <param name="reader"></param>
        /// <param name="saver"></param>
        public virtual void Check(FileReceiver receiver, BinaryReader reader, IFileSaver saver) { }

        /// <summary>
        /// 处理
        /// </summary>
        /// <param name="receiver"></param>
        /// <param name="reader"></param>
        /// <param name="saver"></param>
        public virtual void Handle(FileReceiver receiver, BinaryReader reader, IFileSaver saver) { }
    }

    public class AnonymousReceiverHandle : ReceiverHandle
    {
        public Action<FileReceiver, BinaryReader, IFileSaver> CheckFunc { get; set; }

        public Action<FileReceiver, BinaryReader, IFileSaver> HandleFunc { get; set; }

        public override void Check(FileReceiver receiver, BinaryReader reader, IFileSaver saver)
        {
            CheckFunc?.Invoke(receiver, reader, saver);
        }

        public override void Handle(FileReceiver receiver, BinaryReader reader, IFileSaver saver)
        {
            HandleFunc?.Invoke(receiver, reader, saver);
        }
    }

    /// <summary>
    /// 文件接收器
    /// </summary>
    public class FileReceiver
    {
        /// <summary>
        /// 使用默认的编码
        /// </summary>
        public Encoding Encoding { get; set; } = Encoding.Default;

        /// <summary>
        /// 文件名称
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// 文件长度
        /// </summary>
        private int FileLength { get; set; } = -1;

        /// <summary>
        /// 临时内容长度
        /// </summary>
        public int TempLenght { get; set; } = 1024 * 10;

        /// <summary>
        /// 接口列表
        /// </summary>
        private List<string> ProtocolList { get; set; } = new List<string>();

        //private Dictionary<string, Action<FileReceiver, BinaryReader, IFileSaver>> Name2Reader = new Dictionary<string, Action<FileReceiver, BinaryReader, IFileSaver>>
        //{
        //    // 协议列表
        //    {
        //        PublicKey.TransferInfo.PROTOCOL_LIST, (self, reader, saver) =>
        //        {
        //            var str = reader.ReadString();
        //            var list = str.Split(new char[]{ ';' }, StringSplitOptions.RemoveEmptyEntries);

        //            if(list != null && list.Length > 0)
        //                self.ProtocolList.AddRange(list);
        //        }
        //    },

        //    // 处理编码
        //    {
        //        PublicKey.TransferInfo.TEXT_ENCODE, (self, reader, saver) =>
        //        {
        //            self.Encoding = Encoding.GetEncoding(reader.ReadString());
        //        }
        //    },

        //    // 处理文件名
        //    {
        //        PublicKey.TransferInfo.FILE_NAME, (self, reader, saver) =>
        //        {
        //            saver.SetFileName(reader.ReadString());
        //        }
        //    },

        //    // 处理文件长度
        //    {
        //        PublicKey.TransferInfo.FILE_LENGTH, (self, reader, saver) =>
        //        {
        //            self.FileLength = reader.ReadInt32();
        //        }
        //    },

        //    // 处理文件
        //    {
        //        PublicKey.TransferInfo.FILE, (self, reader, saver) =>
        //        {
        //            if (self.FileLength < 0)
        //                throw new Exception("文件长度出错");

        //            var stream = saver.GetFileStream();
        //            if(stream == null)
        //                throw new Exception("获取文件流失败");

        //            int sum = 0;

        //            while (true)
        //            {
        //                int read_count = Math.Min(self.TempLenght, self.FileLength - sum);
        //                sum += read_count;
        //                var data = reader.ReadBytes(read_count);

        //                stream.Write(data, 0, data.Length);

        //                if(sum >= self.FileLength)
        //                    break;
        //            }
        //        }
        //    },

        //    // 检查文件
        //    {
        //        PublicKey.TransferInfo.CHECK_CODE, (self, reader, saver) =>
        //        {
        //            var checkcode = reader.ReadString();
        //            if (string.IsNullOrEmpty(checkcode))
        //                throw new Exception("检查码不能为空");

        //            var stream = saver.GetFileStream();
        //            if (stream == null)
        //                throw new Exception("获取文件流失败");

        //            string filecode = Md5Helper.GetMD5HashFromStream(stream);

        //            if (filecode != checkcode)
        //                throw new Exception("检查码检查失败");
        //        }
        //    },

        //    // 断开连接
        //    {
        //        PublicKey.TransferInfo.DISCONNECT, (self, reader, saver) => { }
        //    },
        //};

        private Dictionary<string, ReceiverHandle> Name2Reader = new Dictionary<string, ReceiverHandle>
        {
            // 协议列表
            {
                PublicKey.TransferInfo.PROTOCOL_LIST,
                new AnonymousReceiverHandle
                {
                    HandleFunc = (self, reader, saver) =>
                    {
                        var str = reader.ReadString();
                        var list = str.Split(new char[]{ ';' }, StringSplitOptions.RemoveEmptyEntries);

                        if(list != null && list.Length > 0)
                            self.ProtocolList.AddRange(list);
                    }
                }
            },

            // 处理编码
            {
                PublicKey.TransferInfo.TEXT_ENCODE,
                new AnonymousReceiverHandle
                {
                    HandleFunc = (self, reader, saver) =>
                    {
                        self.Encoding = Encoding.GetEncoding(reader.ReadString());
                    }
                }
            },

            // 处理文件名
            {
                PublicKey.TransferInfo.FILE_NAME,
                new AnonymousReceiverHandle
                {
                    HandleFunc = (self, reader, saver) =>
                    {
                        saver.SetFileName(reader.ReadString());
                    }
                }
            },

            // 处理文件长度
            {
                PublicKey.TransferInfo.FILE_LENGTH,
                new AnonymousReceiverHandle
                {
                    HandleFunc = (self, reader, saver) =>
                    {
                        self.FileLength = reader.ReadInt32();
                    }
                }
            },

            // 处理文件
            {
                PublicKey.TransferInfo.FILE,
                new AnonymousReceiverHandle
                {
                    CheckFunc = (self, reader, saver) =>
                    {
                        if (self.FileLength < 0)
                            throw new Exception("文件长度出错");

                        var stream = saver.GetFileStream();
                        if(stream == null)
                            throw new Exception("获取文件流失败");
                    },

                    HandleFunc = (self, reader, saver) =>
                    {
                        var stream = saver.GetFileStream();
                        int sum = 0;
                        while (true)
                        {
                            int read_count = Math.Min(self.TempLenght, self.FileLength - sum);
                            sum += read_count;
                            var data = reader.ReadBytes(read_count);

                            stream.Write(data, 0, data.Length);

                            if(sum >= self.FileLength)
                                break;
                        }
                    },
                }
            },

            // 检查文件
            {
                PublicKey.TransferInfo.CHECK_CODE,

                new AnonymousReceiverHandle
                {
                    HandleFunc = (self, reader, saver) =>
                    {
                        var checkcode = reader.ReadString();
                        if (string.IsNullOrEmpty(checkcode))
                            throw new Exception("检查码不能为空");

                        var stream = saver.GetFileStream();
                        if (stream == null)
                            throw new Exception("获取文件流失败");

                        string filecode = Md5Helper.GetMD5HashFromStream(stream);

                        if (filecode != checkcode)
                            throw new Exception("检查码检查失败");
                    }
                }
            },

            // 断开连接
            {
                PublicKey.TransferInfo.DISCONNECT, new ReceiverHandle()
            },
        };

        /// <summary>
        /// 从流中接收文件
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="saver"></param>
        public string Receive(Stream stream, IFileSaver saver)
        {
            var reader = new BinaryReader(stream);
            var writer = new BinaryWriter(stream);

            List<KeyValuePair<string, ReceiverHandle>> actions = new List<KeyValuePair<string, ReceiverHandle>>();
            actions.Add(new KeyValuePair<string, ReceiverHandle>(PublicKey.TransferInfo.PROTOCOL_LIST, new AnonymousReceiverHandle
            {
                HandleFunc = (_, r, s) =>
                {
                    Name2Reader[PublicKey.TransferInfo.PROTOCOL_LIST]?.Handle(this, r, s);

                    foreach (var item_name in this.ProtocolList)
                        actions.Add(new KeyValuePair<string, ReceiverHandle>(item_name, Name2Reader[item_name]));
                }
            }));

            try
            {
                int index = 0;
                while (true)
                {
                    var action_item = actions[index];

                    Assertion.Assert(reader.ReadString() == PublicHelper.GetStartFlag(action_item.Key), PublicKey.Tips.UNEXPECTED_PROCESS);

                    try
                    {
                        action_item.Value.Check(this, reader, saver);
                        writer.Write(PublicHelper.GetStartFlag(action_item.Key));
                    }
                    catch (Exception ex)
                    {
                        writer.Write(PublicKey.TransferInfo.THROW_ERROR);
                        writer.Write(ex.Message);
                    }

                    action_item.Value?.Handle(this, reader, saver);

                    writer.Write(PublicHelper.GetStopFlag(action_item.Key));
                    Assertion.Assert(reader.ReadString() == PublicHelper.GetStopFlag(action_item.Key), PublicKey.Tips.UNEXPECTED_PROCESS);

                    ++index;

                    if (index >= actions.Count)
                        break;
                }
            }
            catch (Exception ex)
            {
                try
                {
                    writer.Write(PublicKey.TransferInfo.THROW_ERROR);
                    writer.Write(ex.Message);
                }
                catch (Exception back_ex)
                {
                    Console.WriteLine(back_ex.Message);
                    Console.WriteLine(back_ex.StackTrace);
                }
            }
            finally
            {
                try
                {
                    var file_stream = saver.GetFileStream();
                    file_stream?.Close();
                }
                catch (Exception close_ex)
                {
                    Console.WriteLine(close_ex.Message);
                    Console.WriteLine(close_ex.StackTrace);
                }
            }

            try
            {
                var action_name = reader.ReadString();
                writer.Write(PublicKey.TransferInfo.DISCONNECT);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
            finally
            {
                reader.Close();
                writer.Close();
                stream.Close();
            }

            return saver.GetFileFullPath();
        }
    }

    /// <summary>
    /// 文件接收器
    /// </summary>
    public class FileSender
    {
        /// <summary>
        /// 编码
        /// </summary>
        public Encoding Encoding { get; set; } = Encoding.Default;

        /// <summary>
        /// 临时内容长度
        /// 
        /// </summary>
        public int TempLenght { get; set; } = 1024 * 10;

        private Dictionary<string, Action<FileSender, BinaryWriter, IFileReader>> Name2Reader = new Dictionary<string, Action<FileSender, BinaryWriter, IFileReader>>
        {
            // 协议列表
            {
                PublicKey.TransferInfo.PROTOCOL_LIST, (self, writer, reader) =>
                {
                    //List<string> protocols = new List<string>
                    //{
                    //    PublicKey.TransferInfo.TEXT_ENCODE,
                    //    PublicKey.TransferInfo.FILE_NAME,
                    //    PublicKey.TransferInfo.FILE_LENGTH,
                    //    PublicKey.TransferInfo.FILE,
                    //    PublicKey.TransferInfo.CHECK_CODE,
                    //};

                    var list = new List<string>();
                    list.AddRange(self.Name2Reader.Keys);
                    list.RemoveAt(0);
                    writer.Write(string.Join(";", list));
                }
            },

            // 处理编码
            {
                PublicKey.TransferInfo.TEXT_ENCODE, (self, writer, reader) =>
                {
                    writer.Write(self.Encoding.BodyName);
                }
            },

            // 处理文件名
            {
                PublicKey.TransferInfo.FILE_NAME, (self, writer, reader) =>
                {
                    writer.Write(Path.GetFileName(reader.GetFileName()));
                }
            },

            // 处理文件长度
            {
                PublicKey.TransferInfo.FILE_LENGTH, (self, writer, reader) =>
                {
                    var stream = reader.GetFileStream();
                    if(stream == null)
                        throw new Exception("没有找到文件流");

                    if(stream.Length > Int32.MaxValue)
                        throw new Exception("传输文件超过最大允许长度");

                    writer.Write((Int32)stream.Length);
                }
            },

            // 处理文件
            {
                PublicKey.TransferInfo.FILE, (self, writer, reader) =>
                {
                    var stream = reader.GetFileStream();
                    if(stream == null)
                        throw new Exception("没有找到文件流");

                    if(stream.Length > Int32.MaxValue)
                        throw new Exception("传输文件超过最大允许长度");

                    if(stream.CanSeek)
                        stream.Seek(0, SeekOrigin.Begin);

                    byte[] data = new byte[self.TempLenght];

                    while (stream.Position < stream.Length)
                    {
                        var r_count = stream.Read(data, 0, data.Length);
                        writer.Write(data, 0, r_count);
                    }
                }
            },

            // 检查文件
            {
                PublicKey.TransferInfo.CHECK_CODE, (self, writer, reader) =>
                {
                    var stream = reader.GetFileStream();
                    if(stream == null)
                        throw new Exception("没有找到文件流");

                    string checkcode = Md5Helper.GetMD5HashFromStream(stream);

                    if (string.IsNullOrEmpty(checkcode))
                        throw new Exception("检查码不能为空");

                    writer.Write(checkcode);
                }
            },

            // 断开连接
            {
                PublicKey.TransferInfo.DISCONNECT, (self, reader, saver) => { }
            },
        };

        /// <summary>
        /// 向流中写入文件信息
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="reader"></param>
        public void Send(Stream stream, IFileReader reader)
        {
            var stream_reader = new BinaryReader(stream);
            var stream_writer = new BinaryWriter(stream);

            try
            {
                foreach (var item in Name2Reader)
                {
                    stream_writer.Write(PublicHelper.GetStartFlag(item.Key));
                    string action_name = stream_reader.ReadString();

                    // 服务端回传错误
                    if (action_name == PublicKey.TransferInfo.THROW_ERROR)
                    {
                        var error_message = stream_reader.ReadString();
                        throw new Exception(error_message);
                    }

                    Assertion.Assert(action_name == PublicHelper.GetStartFlag(item.Key), PublicKey.Tips.UNEXPECTED_PROCESS);

                    item.Value?.Invoke(this, stream_writer, reader);

                    action_name = stream_reader.ReadString();

                    // 服务端回传错误
                    if (action_name == PublicKey.TransferInfo.THROW_ERROR)
                    {
                        var error_message = stream_reader.ReadString();
                        throw new Exception(error_message);
                    }

                    Assertion.Assert(action_name == PublicHelper.GetStopFlag(item.Key), PublicKey.Tips.UNEXPECTED_PROCESS);
                    stream_writer.Write(PublicHelper.GetStopFlag(item.Key));
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
            finally
            {
                try
                {
                    var file_stream = reader.GetFileStream();
                    file_stream.Close();
                }
                catch (Exception back_ex)
                {
                    Console.WriteLine(back_ex.Message);
                    Console.WriteLine(back_ex.StackTrace);
                }
            }

            try
            {
                stream_writer.Write(PublicKey.TransferInfo.DISCONNECT);
                var disconnect = stream_reader.ReadString();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
            finally
            {
                stream_reader.Close();
                stream_writer.Close();
                stream.Close();
            }

            Console.WriteLine($"发送文件完成 {reader.GetFileName()}");
        }
    }

    /// <summary>
    /// 文件读取接口
    /// </summary>
    public interface IFileReader
    {
        /// <summary>
        /// 获取文件名
        /// </summary>
        /// <returns></returns>
        string GetFileName();

        /// <summary>
        /// 获取文件流
        /// </summary>
        /// <returns></returns>
        Stream GetFileStream();
    }

    /// <summary>
    /// 文件保存接口
    /// </summary>
    public interface IFileSaver
    {
        /// <summary>
        /// 设置文件名
        /// </summary>
        /// <param name="filename"></param>
        void SetFileName(string filename);

        /// <summary>
        /// 获取文件流
        /// </summary>
        /// <returns></returns>
        Stream GetFileStream();

        /// <summary>
        /// 获取保存文件的全路径
        /// </summary>
        /// <returns></returns>
        string GetFileFullPath();
    }


    public static class Md5Helper
    {
        /// <summary>
        /// 从流中获取哈希值
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static string GetMD5HashFromStream(Stream stream)
        {
            try
            {
                System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                byte[] retVal = md5.ComputeHash(stream);

                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < retVal.Length; i++)
                {
                    sb.Append(retVal[i].ToString("x2"));
                }
                return sb.ToString();
            }
            catch (Exception ex)
            {
                throw new Exception($"{nameof(GetMD5HashFromStream)} fail,error:{ex.Message}");
            }
        }

        /// <summary>
        /// 从文件中获取Md5值
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static string GetMD5HashFromFile(string fileName)
        {
            string code = string.Empty;
            try
            {
                FileStream file = new FileStream(fileName, FileMode.Open);
                code = GetMD5HashFromStream(file);
                file.Close();
            }
            catch (Exception ex)
            {
                throw new Exception($"{nameof(GetMD5HashFromFile)} fail,error:{ex.Message}");
            }

            return code;
        }

    }

    public static class PublicHelper
    {
        public static string GetStartFlag(string name)
        {
            return $"{name}::Start";
        }

        public static string GetStopFlag(string name)
        {
            return $"{name}::Stop";
        }
    }

    /// <summary>
    /// 公开Key
    /// </summary>
    public static class PublicKey
    {
        /// <summary>
        /// 传输信息
        /// </summary>
        public static class TransferInfo
        {
            /// <summary>
            /// 协议列表（规定后续的）
            /// </summary>
            public const string PROTOCOL_LIST = "ProtocolList";

            /// <summary>
            /// 文本内容编码
            /// </summary>
            public const string TEXT_ENCODE = "TextEncode";

            /// <summary>
            /// 文件名称
            /// </summary>
            public const string FILE_NAME = "FileName";

            /// <summary>
            /// 文件长度
            /// </summary>
            public const string FILE_LENGTH = "FileLength";

            /// <summary>
            /// 文件
            /// </summary>
            public const string FILE = "File";

            /// <summary>
            /// 检查编码
            /// </summary>
            public const string CHECK_CODE = "CheckCode";

            /// <summary>
            /// 抛出异常
            /// </summary>
            public const string THROW_ERROR = "ThrowError";

            /// <summary>
            /// 断开连接
            /// </summary>
            public const string DISCONNECT = "Disconnect";
        }

        public static class Tips
        {
            public const string UNEXPECTED_PROCESS = "意外的流程";
        }
    }
}
